﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Globalization;
using System.Linq;
using DslModeling=global::Microsoft.VisualStudio.Modeling;
using DslDesign=global::Microsoft.VisualStudio.Modeling.Design;
using DslDiagrams=global::Microsoft.VisualStudio.Modeling.Diagrams;
using DslValidation=global::Microsoft.VisualStudio.Modeling.Validation;

namespace Salamanca.Sdml
{
    [DslValidation.ValidationState(DslValidation.ValidationState.Enabled)]
    partial class DomainEntity:
        IDataTableHolder,
        IComparable<DomainEntity>
    {

        //partial class InheritanceModifierPropertyHandler
        //{
        //    protected override void OnValueChanged(DomainEntity element, InheritanceModifier oldValue, InheritanceModifier newValue)
        //    {
        //        foreach (DslDiagrams.PresentationElement pe in DslDiagrams.PresentationViewsSubject.GetPresentation(element))
        //        {
        //            DslDiagrams.ShapeElement shape=pe as DslDiagrams.ShapeElement;
        //            if (shape!=null)
        //                shape.Invalidate();
        //        }

        //        base.OnValueChanged(element, oldValue, newValue);
        //    }
        //}

        public IDictionary<DomainEntity, IList> Relations
        {
            [global::System.Diagnostics.DebuggerStepThrough]
            get
            {
                Dictionary<DomainEntity, IList> ret=new Dictionary<DomainEntity,IList>();

                // All forward associations
                foreach (Association a in Association.GetLinksToSources(this))
                    _AddRelation(ret, a.Source, a);

                // All backward non uni-directional associations
                foreach (Association a in Association.GetLinksToTargets(this))
                    if (a.Form!=AssociationForm.UniDirectional)
                        _AddRelation(ret, a.Target, a);

                // All multiple associations
                foreach (MultipleAssociationRole mar in MultipleAssociationRole.GetLinksToMultipleAssociations(this))
                    foreach (DomainEntity e in mar.MultipleAssociation.Types)
                        if (e!=this)
                            _AddRelation(ret, e, MultipleAssociationRole.GetLinksToTypes(mar.MultipleAssociation));

                return ret;
            }
        }

        [DslValidation.ValidationMethod(DslValidation.ValidationCategories.Menu | DslValidation.ValidationCategories.Open | DslValidation.ValidationCategories.Save)]
        private void ValidateInheritance(DslValidation.ValidationContext context)
        {
            switch (InheritanceModifier)
            {
            case InheritanceModifier.Abstract:
                if (SubEntities.Count==0)
                    context.LogError(Resources.SR.InheritanceAbstractMustHaveChildren, "SDML03", this);
                break;
            case InheritanceModifier.Sealed:
                if (SubEntities.Count!=0)
                    context.LogError(Resources.SR.InheritanceSealedCannotHaveChildren, "SDML03", this);
                break;
            }
        }

        [DslValidation.ValidationMethod(DslValidation.ValidationCategories.Menu | DslValidation.ValidationCategories.Open | DslValidation.ValidationCategories.Save)]
        private void ValidateAttributesName(DslValidation.ValidationContext context)
        {
            if (Attributes.Count==0)
                context.LogWarning(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.SR.EntityShouldHaveAttribute,
                        this.Name ?? string.Empty
                    ),
                    "SDML06",
                    new DslModeling.ModelElement[] { this }
                );

            if (Parent!=null)
                return;

            Dictionary<string, IList<DslModeling.ModelElement>> attributes=new Dictionary<string, IList<Microsoft.VisualStudio.Modeling.ModelElement>>(StringComparer.InvariantCultureIgnoreCase);
            ValidateAttributesName(attributes, this, context);

            foreach (IList<DslModeling.ModelElement> duplicates in attributes.Values.Where(list => list.Count>1))
                context.LogError(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Resources.SR.AttributeNameCannotBeDuplicatedInHierarchy,
                        ((EntityAttribute)duplicates[0]).Name
                    ),
                    "SDML05",
                    duplicates.ToArray()
                );
        }

        private void ValidateAttributesName(IDictionary<string, IList<DslModeling.ModelElement>> attributes, DomainEntity entity, DslValidation.ValidationContext context)
        {
            foreach (EntityAttribute attribute in entity.Attributes)
            {
                if (!attributes.ContainsKey(attribute.Name))
                    attributes.Add(attribute.Name, new List<DslModeling.ModelElement>(new DslModeling.ModelElement[] { attribute }));
                else
                    attributes[attribute.Name].Add(attribute);
            }

            foreach (DomainEntity de in entity.SubEntities)
                ValidateAttributesName(attributes, de, context);
        }

        private void _AddRelation(IDictionary<DomainEntity, IList> relations, DomainEntity related, object link)
        {
            if (relations.ContainsKey(related))
            {
                ((IList)relations[related]).Add(link);
            } else
                relations.Add(related, new List<object>(new object[] { link }));
        }

        public string GetPluralName()
        {
            return string.IsNullOrEmpty(PluralName) ? string.Concat(Name, "s") : PluralName;
        }

        internal string TableName
        {
            get
            {
                return GetPluralName();
            }
        }

        public DataTable DataTable
        {
            get
            {
                DomainModel.LoadDataSet();
                return (DataTable)_TableReference.Target;
            }
            set
            {
                _TableReference=new WeakReference(value);
            }
        }

        internal SdmlType IdType
        {
            get
            {
                return _IdType;
            }
        }

        private static bool References(DomainEntity current, DomainEntity entity, IDictionary<Guid, DomainEntity> visited)
        {
            if (current==entity)
                return true;
            if (visited.ContainsKey(current.Id))
                return false;
            visited.Add(current.Id, current);

            // Inheritance
            if (current.Parent!=null)
            {
                if (References(current.Parent, entity, visited))
                    return true;
            }

            // Multiple association
            if (current.MultipleAssociation!=null)
                foreach (DomainEntity de in current.MultipleAssociation.Types)
                {
                    if (References(de, entity, visited))
                        return true;
                }

            // References
            List<Association> raList=new List<Association>(
                DslModeling::DomainRoleInfo.GetElementLinks<Association>(
                    current,
                    Association.TargetDomainRoleId
                )
            ).FindAll(
                delegate(Association a) {
                    return Association.IsSingleMultiplicity(a.SourceMultiplicity);
                }
            );
            foreach (Association a in raList)
                if (References(a.Source, entity, visited))
                    return true;

            return false;
        }

        internal bool References(DomainEntity entity)
        {
            SortedList<Guid, DomainEntity> list=new SortedList<Guid, DomainEntity>();
            return References(this, entity, list);
        }

        internal int CompareTo(DomainEntity other)
        {
            if (other==null)
                return 1;

            if (References(other))
                return 1;
            if (other.References(this))
                return -1;

            if ((MultipleAssociation!=null) && (other.MultipleAssociation==null))
                return -1;
            if ((MultipleAssociation==null) && (other.MultipleAssociation!=null))
                return 1;

            return Math.Sign(other.Relations.Count-Relations.Count);
        }

        int IComparable<DomainEntity>.CompareTo(DomainEntity other)
        {
            return CompareTo(other);
        }

        private SdmlType _IdType=new GuidType();
        private WeakReference _TableReference;
    }
}
